package mongo

import (
	"context"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"mychat/internal/common/logger"
	"mychat/internal/conf"
)

type Mongo interface {
	CreateChat(ctx context.Context, ct *Chat) error
	GetChat(ctx context.Context, chatId string) (*Chat, error)
	ListChats(ctx context.Context, param *GetChatsParam) (int64, []*Chat, error)
	ChangeChatName(ctx context.Context, chatId, newName string) error
	DeleteChat(ctx context.Context, chatId string) error
	DeleteChatMember(ctx context.Context, chatId string, memberId uint) error
	AddChatMember(ctx context.Context, chatId string, user *ChatUser) error
}

type mongodb struct {
	db *mongo.Database
	mc *conf.Mongo
	lg logger.Logger
}

func NewMongo(mc *conf.Mongo, lg logger.Logger) Mongo {
	return &mongodb{
		db: createAndInitClient(mc),
		mc: mc,
		lg: lg.WithCustomTag(logger.LoggerTagMongo),
	}
}

func createAndInitClient(mc *conf.Mongo) *mongo.Database {
	// 设置客户端选项
	clientOptions := options.Client().ApplyURI(mc.Dsn)
	clientOptions.SetMaxPoolSize(mc.MaxPool)
	clientOptions.SetMinPoolSize(mc.MinPool)
	clientOptions.Auth = &options.Credential{Username: mc.UserName, Password: mc.Password}

	// 连接到MongoDB
	client, err := mongo.Connect(context.TODO(), clientOptions)
	if err != nil {
		panic(err)
	}

	// 检查连接
	err = client.Ping(context.TODO(), nil)
	if err != nil {
		panic(err)
	}

	mdb := client.Database(mc.Db)

	err = Chat{}.Init(mdb)
	if err != nil {
		panic(err)
	}
	return mdb
}

func (s *mongodb) CreateChat(ctx context.Context, cht *Chat) error {
	cht.ChatId = primitive.NewObjectID().Hex()
	bsChat, err := bson.Marshal(cht)
	if err != nil {
		return err
	}

	_, err = s.db.Collection(cht.CollectionName()).InsertOne(ctx, bsChat)
	if err != nil {
		return err
	}

	return nil
}

func (s *mongodb) GetChat(ctx context.Context, chatId string) (*Chat, error) {

	var cht Chat
	ret := s.db.Collection(cht.CollectionName()).FindOne(ctx, bson.M{"_id": chatId})
	err := ret.Err()
	if err != nil {
		return nil, err
	}

	err = ret.Decode(&cht)
	if err != nil {
		return nil, err
	}

	return &cht, nil
}

func (s *mongodb) ListChats(ctx context.Context, param *GetChatsParam) (int64, []*Chat, error) {

	collection := s.db.Collection(Chat{}.CollectionName())
	filter := bson.M{"owner": param.UserId}
	cnt, err := collection.CountDocuments(ctx, filter)
	if err != nil {
		return 0, nil, err
	}

	opt := options.Find()
	if param.Skip > 0 {
		opt.SetSkip(param.Skip)
	}
	if param.Limit > 0 {
		opt.SetLimit(param.Limit)
	}

	if len(param.Order) > 0 {
		opt.SetSort(param.Order)
	}

	cur, err := collection.Find(ctx, filter, opt)
	if err != nil {
		return 0, nil, err
	}

	var rets []*Chat
	err = cur.All(ctx, &rets)
	if err != nil {
		return 0, nil, err
	}

	return cnt, rets, nil
}

func (s *mongodb) DeleteChat(ctx context.Context, chatId string) error {

	collname := Chat{}.CollectionName()
	_, err := s.db.Collection(collname).DeleteOne(ctx, bson.M{"_id": chatId})
	if err != nil {
		return err
	}

	return nil
}

func (s *mongodb) DeleteChatMember(ctx context.Context, chatId string, memberId uint) error {

	collname := Chat{}.CollectionName()

	filter := bson.M{"_id": chatId}
	update := bson.D{{"$pull", bson.D{
		{"users", bson.D{{
			"user_id", memberId}}},
	}}}

	_, err := s.db.Collection(collname).UpdateOne(ctx, filter, update)
	if err != nil {
		return err
	}

	return nil
}

func (s *mongodb) AddChatMember(ctx context.Context, chatId string, user *ChatUser) error {

	collname := Chat{}.CollectionName()

	filter := bson.M{"_id": chatId}
	update := bson.D{{"$push", bson.D{
		{"users", bson.D{
			{"user_id", user.UserId},
			{"name", user.Name},
		}},
	}}}

	_, err := s.db.Collection(collname).UpdateOne(ctx, filter, update)
	if err != nil {
		return err
	}

	return nil
}

func (s *mongodb) ChangeChatName(ctx context.Context, chatId, newName string) error {

	collname := Chat{}.CollectionName()

	filter := bson.M{"_id": chatId}
	update := bson.M{"chat_name": newName}

	_, err := s.db.Collection(collname).UpdateOne(ctx, filter, update)
	if err != nil {
		return err
	}

	return nil
}
